using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace UAOOI.Networking.ReferenceApplication.Core.MvvmLight
{
  /// <summary>
  /// An <see cref="ICommand"/> whose delegates can be attached for <see cref="Execute"/> and <see cref="CanExecute"/>.
  /// </summary>
  /// <typeparam name="T">Parameter type.</typeparam>
  /// <remarks>
  /// The constructor deliberately prevents the use of value types.
  /// Because ICommand takes an object, having a value type for T would cause unexpected behavior when CanExecute(null) is called during XAML initialization for command bindings.
  /// Using default(T) was considered and rejected as a solution because the implementor would not be able to distinguish between a valid and defaulted values.
  /// <para/>
  /// Instead, callers should support a value type by using a nullable value type and checking the HasValue property before using the Value property.
  /// <example>
  ///     <code>
  /// public MyClass()
  /// {
  ///     this.submitCommand = new DelegateCommand&lt;int?&gt;(this.Submit, this.CanSubmit);
  /// }
  /// 
  /// private bool CanSubmit(int? customerId)
  /// {
  ///     return (customerId.HasValue &amp;&amp; customers.Contains(customerId.Value));
  /// }
  ///     </code>
  /// </example>
  /// </remarks>
  public class DelegateCommand<T> : DelegateCommandBase
  {

    /// <summary>
    /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
    /// </summary>
    /// <param name="executeMethod">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
    /// <remarks><see cref="CanExecute"/> will always return true.</remarks>
    public DelegateCommand(Action<T> executeMethod)
        : this(executeMethod, (o) => true)
    { }
    /// <summary>
    /// Initializes a new instance of <see cref="DelegateCommand{T}"/>.
    /// </summary>
    /// <param name="executeMethod">Delegate to execute when Execute is called on the command. This can be null to just hook up a CanExecute delegate.</param>
    /// <param name="canExecuteMethod">Delegate to execute when CanExecute is called on the command. This can be null.</param>
    /// <exception cref="ArgumentNullException">When both <paramref name="executeMethod"/> and <paramref name="canExecuteMethod"/> ar <see langword="null" />.</exception>
    public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
        : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
    {
      if (executeMethod == null || canExecuteMethod == null)
        throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);
      TypeInfo _genericTypeInfo = typeof(T).GetTypeInfo();
      // DelegateCommand allows object or Nullable<>.  
      // note: Nullable<> is a struct so we cannot use a class constraint.
      if (_genericTypeInfo.IsValueType)
      {
        if ((!_genericTypeInfo.IsGenericType) || (!typeof(Nullable<>).GetTypeInfo().IsAssignableFrom(_genericTypeInfo.GetGenericTypeDefinition().GetTypeInfo())))
        {
          throw new InvalidCastException(Resources.DelegateCommandInvalidGenericPayloadType);
        }
      }

    }
    /// <summary>
    /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    /// </summary>
    /// <typeparam name="TP">The object type containing the property specified in the expression.</typeparam>
    /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
    /// <returns>The current instance of DelegateCommand</returns>
    public DelegateCommand<T> ObservesProperty<TP>(Expression<Func<TP>> propertyExpression)
    {
      ObservesPropertyInternal(propertyExpression);
      return this;
    }
    /// <summary>
    /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    /// </summary>
    /// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute((o) => PropertyName).</param>
    /// <returns>The current instance of DelegateCommand</returns>
    public DelegateCommand<T> ObservesCanExecute(Expression<Func<object, bool>> canExecuteExpression)
    {
      ObservesCanExecuteInternal(canExecuteExpression);
      return this;
    }
    ///<summary>
    ///Determines if the command can execute by invoked the <see cref="Func{T,Bool}"/> provided during construction.
    ///</summary>
    ///<param name="parameter">Data used by the command to determine if it can execute.</param>
    ///<returns>
    ///<see langword="true" /> if this command can be executed; otherwise, <see langword="false" />.
    ///</returns>
    public virtual bool CanExecute(T parameter)
    {
      return base.CanExecute(parameter);
    }
    ///<summary>
    ///Executes the command and invokes the <see cref="Action{T}"/> provided during construction.
    ///</summary>
    ///<param name="parameter">Data used by the command.</param>
    public virtual Task Execute(T parameter)
    {
      return base.Execute(parameter);
    }

    #region private constructors
    protected DelegateCommand(Func<T, Task> executeMethod)
    : this(executeMethod, (o) => true)
    { }
    protected DelegateCommand(Func<T, Task> executeMethod, Func<T, bool> canExecuteMethod)
        : base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
    {
      if (executeMethod == null || canExecuteMethod == null)
        throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);
    }
    #endregion

  }

  /// <summary>
  /// An <see cref="ICommand"/> whose delegates do not take any parameters for <see cref="Execute"/> and <see cref="CanExecute"/>.
  /// </summary>
  /// <see cref="DelegateCommandBase"/>
  /// <see cref="DelegateCommand{T}"/>
  public class DelegateCommand : DelegateCommandBase
  {

    #region constructors
    /// <summary>
    /// Creates a new instance of <see cref="DelegateCommand"/> with the <see cref="Action"/> to invoke on execution.
    /// </summary>
    /// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute"/> is called.</param>
    public DelegateCommand(Action executeMethod)
        : this(executeMethod, () => true)
    { }

    /// <summary>
    /// Creates a new instance of <see cref="DelegateCommand"/> with the <see cref="Action"/> to invoke on execution
    /// and a <see langword="Func" /> to query for determining if the command can execute.
    /// </summary>
    /// <param name="executeMethod">The <see cref="Action"/> to invoke when <see cref="ICommand.Execute"/> is called.</param>
    /// <param name="canExecuteMethod">The <see cref="Func{TResult}"/> to invoke when <see cref="ICommand.CanExecute"/> is called</param>
    public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
        : base((o) => executeMethod(), (o) => canExecuteMethod())
    {
      if (executeMethod == null || canExecuteMethod == null)
        throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);
    }
    #endregion    

    /// <summary>
    /// Observes a property that implements INotifyPropertyChanged, and automatically calls DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    /// </summary>
    /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
    /// <param name="propertyExpression">The property expression. Example: ObservesProperty(() => PropertyName).</param>
    /// <returns>The current instance of DelegateCommand</returns>
    public DelegateCommand ObservesProperty<T>(Expression<Func<T>> propertyExpression)
    {
      ObservesPropertyInternal(propertyExpression);
      return this;
    }
    /// <summary>
    /// Observes a property that is used to determine if this command can execute, and if it implements INotifyPropertyChanged it will automatically call DelegateCommandBase.RaiseCanExecuteChanged on property changed notifications.
    /// </summary>
    /// <param name="canExecuteExpression">The property expression. Example: ObservesCanExecute((o) => PropertyName).</param>
    /// <returns>The current instance of DelegateCommand</returns>
    public DelegateCommand ObservesCanExecute(Expression<Func<object, bool>> canExecuteExpression)
    {
      ObservesCanExecuteInternal(canExecuteExpression);
      return this;
    }
    ///<summary>
    /// Executes the command.
    ///</summary>
    public virtual Task Execute()
    {
      return Execute(null);
    }
    /// <summary>
    /// Determines if the command can be executed.
    /// </summary>
    /// <returns>Returns <see langword="true"/> if the command can execute, otherwise returns <see langword="false"/>.</returns>
    public virtual bool CanExecute()
    {
      return CanExecute(null);
    }

    #region private constructors
    protected DelegateCommand(Func<Task> executeMethod)
    : this(executeMethod, () => true)
    { }
    protected DelegateCommand(Func<Task> executeMethod, Func<bool> canExecuteMethod)
        : base((o) => executeMethod(), (o) => canExecuteMethod())
    {
      if (executeMethod == null || canExecuteMethod == null)
        throw new ArgumentNullException(nameof(executeMethod), Resources.DelegateCommandDelegatesCannotBeNull);
    }
    #endregion

  }
  internal static class Resources
  {
    internal static readonly string DelegateCommandDelegatesCannotBeNull = "Neither the executeMethod nor the canExecuteMethod delegates can be null.";
    internal static readonly string DelegateCommandInvalidGenericPayloadType = @"T for DelegateCommand<T> is not an object nor Nullable.";
    internal static readonly string PropertySupport_NotMemberAccessExpression_Exception = "The expression is not a member access expression.";
    internal static readonly string PropertySupport_ExpressionNotProperty_Exception = "The member access expression does not access a property.";
    internal static readonly string PropertySupport_StaticExpression_Exception ="The referenced property is a static property.";
  }
}
